#include <stdlib.h>
#include <stdio.h>
#include <dirent.h>
#include <string.h>
#include <getopt.h>

#include "GeneradorIndice.h"
#include "vector_dinamico.h"
#include "vector_binario.h"
#include "Indice.h"
#include "Merger.h"
#include "ManejadorBits.h"
#include "codigos.h"
#include "Indice_mem.h"

/* *********************************************************************
 *                	 	  		CONSTANTES							   *
 * *********************************************************************/

#define TAM_INICIAL 10

// archivo de documentos
#define DOCUMENTOS "documentos.dat"

// carpeta donde van a ir los indices
#define INDICES "Indices"
#define MERGE_CANT_MAX 14

// extension de los archivos indice
#define EXTENSION ".mnd"

// LECTURA DE COMANDOS POR CONSOLA
#define OPCIONES "r:d:q:"
#define REPOSITORIO 'r'
#define DIRECTORIO 'd'
#define CONSULTA 'q'
#define CANTIDAD_OPC 3

#define REPOSITORIO_POS 0
#define DIRECTORIO_POS 1
#define CONSULTA_POS 2
// --------------------------------

/* *********************************************************************
 *                	 	  FUNCIONES AUXILIARES						   *
 * *********************************************************************/

// Redimensiona la cadena pasada al tamaño nuevo.
// Pre: todos los parámetros son válidos.
static bool cadena_redimensionar (char** cad, size_t nuevo_tam) {
	char* nueva = realloc ((*cad),nuevo_tam*sizeof(char));
	if (!nueva) return false;
	(*cad) = nueva;
	return true;
}

// Concatena las dos cadenas poniendo primero cadenaInicial y luego cadenaFinal.
// Si 'barra' es true, se agrega el caracter '/' entre ambos strings,
// a menos que cadenaInicial ya lo posea al final de ella.
// Pre: ambas cadenas son válidas.
// Post: se devolvió la concatenación o NULL en caso de error.
// ATENCIÓN: La memoria pedida para la cadena devuelta debe ser correctamente
// liberada por el usuario.
char* concatenacion_cadenas (char* cadenaInicial, char* cadenaFinal, bool barra) {
	if ( (!cadenaInicial) || (!cadenaFinal) ) return NULL;
	size_t largo = 30; size_t i = 0; size_t j = 0;
	char* cad = malloc (largo*sizeof(char));
	if (!cad) return NULL;
	
	while (cadenaInicial[i] != '\0') { // copio el primer directorio
		cad[i] = cadenaInicial[i];
		i++;
		if (i == largo) {
			largo = 3*i;
			cadena_redimensionar (&cad,largo);
		}
	}
	// si el primer directorio no termina en barra y hay que agregarla:
	if ( ( i == 0 ) && (barra) ) { // la primer cadena estaba vacía
		cad[i] = '/';
		i++;
	} else if ( (cadenaInicial[i-1] != '/') && (barra) ) {
		cad[i] = '/';
		i++;
	}
	while (cadenaFinal[j] != '\0') { // copio el segundo directorio
		cad[i+j] = cadenaFinal[j];
		j++;
		if (i+j == largo-1) {
			largo = 3*(i+j);
			cadena_redimensionar (&cad,largo);
		}
	}
	cad[i+j] = '\0'; cadena_redimensionar (&cad,i+j+1);
	return cad;
}

int cantidad_digitos (int num) {
	if (num < 0) num *= -1;
	int divisor = 1;
	int cociente = num / divisor;
	int digitos = 1;
	while (cociente >= 10) {
		digitos++;
		divisor *= 10;
		cociente = num / divisor;
	}
	return digitos;
}

/* Analiza si la cadena pasada por parámetro es un directorio o no.
 * Pre: la cadena es válida. */
bool existe_directorio (char* ruta) {
	DIR* directorio = opendir (ruta);
	if (!directorio) return false;
	closedir (directorio);
	return true;
}

/* Crea un directorio en la ruta "directorioPadre/nombreCarpeta".
 * IMPORTANTE: 'directorioPadre' debe ser absoluto, es decir, debe comenzar
 * con '/'. */
void creacionDirectorio (char* directorioPadre, char* nombreCarpeta) {
	if ( (!directorioPadre) || (!nombreCarpeta) ) return;
	char* directorioInicial = "";
	bool destruir = false;

	directorioInicial = directorioPadre;
	char* ruta = concatenacion_cadenas (directorioInicial, nombreCarpeta, true);
	char* comando = concatenacion_cadenas ("mkdir ", ruta, false);
	
	if (!existe_directorio (ruta)) {
		system (comando);
	}
	free (comando);
	free (ruta);
	if (destruir) free (directorioInicial);
}

void quemar_indice (void* dato) {
	Indice_destruir ( (Indice_t*) dato );
}

void destruir_archivos (Indice_t* indice) {
	vector_dinamico_t* lexicos = Indice_verRutasLexico (indice);
	vector_dinamico_t* datos   = Indice_verRutasDatos (indice);
	int largo = vector_cantidad (lexicos);
	for ( int i = 0 ; i < largo ; i++ ) {
		char* rutaLexico = vector_obtener ( lexicos, i );
		char* rutaDatos  = vector_obtener ( datos  , i );
		remove (rutaLexico);
		remove (rutaDatos);
	}
}

void destruir_archivos_indices (vector_dinamico_t* indices) {
	int largo = vector_cantidad (indices);
	for ( int i = 0 ; i < largo ; i++ ) {
		Indice_t* indice = vector_obtener (indices, i);
		destruir_archivos (indice);
	}
}

/* *********************************************************************
 *                	 	 CONSTRUCCION DEL INDICE					   *
 * *********************************************************************/

/* Crea el archivo de documentos en el directorio pasado por parametro. */
ManejadorBits_t* crear_manejadorDocs (char* directorioIndice) {
	char* ruta = concatenacion_cadenas (directorioIndice, DOCUMENTOS, true);
	if (!ruta) return NULL;
	ManejadorBits_t* manejador =  ManejadorBits_crear (ruta, true);
	free (ruta);
	return manejador;
}

void escribir_doc (ManejadorBits_t* docs, char* nombreArchivo, int longNombre,int* offset) {
	vector_binario_t* numeroGamma = cod_gamma_nuevo (longNombre);
	ManejadorBits_escribir_numero (docs, numeroGamma);
	
	ManejadorBits_escribir_cadena(docs, nombreArchivo);
	
	*offset += ( v_binario_largo (numeroGamma) + 8 * (strlen(nombreArchivo)) );
	v_binario_destruir (numeroGamma);
}

Indice_t* procesar_documento (char* ruta, int offset) {
	if (!ruta) return NULL;
	GeneradorIndice_t* generador = GeneradorIndice_crear (ruta, offset);
	if (!generador) return NULL;
	
	GeneradorIndice_indexar (generador);
	vector_dinamico_t* lexico = GeneradorIndice_verRutasLexico (generador);
	vector_dinamico_t* datos = GeneradorIndice_verRutasDatos (generador);
	Indice_t* indices = Indice_crear (lexico, datos);
	GeneradorIndice_destruir (generador);
	return indices;
}

bool procesar_archivo (char* direccionRepositorio, ManejadorBits_t* docs, vector_dinamico_t* indices, char* nombreArchivo, int* offset_doc) {
	int longNombre = strlen (nombreArchivo);
	int offset =*offset_doc;
	escribir_doc (docs, nombreArchivo, longNombre, offset_doc);
	
	char* direccionArchivo = concatenacion_cadenas (direccionRepositorio, nombreArchivo, true);
	Indice_t* indice = procesar_documento (direccionArchivo, offset);	
	
	if (!indice) return false;
	
	vector_guardar (indices, vector_cantidad (indices), indice);
	if ((vector_cantidad(indices)) == (vector_tamanio(indices))) {
		vector_redimensionar (indices, (vector_cantidad (indices)) * 2);
	}
	
	return true;
}

char* generacion_cadena (int num, char* VueltaGlobal, char* cadenaFinal) {
	int largo = cantidad_digitos (num);
	char numeroVuelta[largo];
	sprintf (numeroVuelta, "%d", num);
	char* cadena_parcial_1 = concatenacion_cadenas (numeroVuelta, "_vuelta_", false);
	char* cadena_parcial_2 = concatenacion_cadenas (cadena_parcial_1, VueltaGlobal, false);
	char* resultado = concatenacion_cadenas (cadena_parcial_2, cadenaFinal, false);
	
	free (cadena_parcial_1); free (cadena_parcial_2);
	return resultado;
}

bool realizar_merge_vuelta (vector_dinamico_t* indices, vector_dinamico_t* indices_mergeados,
							int largo, int vuelta, char* cadenaVueltaGlobal, char* nombreRepositorio) {
	
	vector_dinamico_t* indices_vuelta = vector_crear (largo, NULL);
	if (!indices_vuelta) return false;
	
	for ( int j = 0 ; j < largo ; j++ ) {
		Indice_t* IndiceASacar = vector_obtener ( indices, j + ( vuelta * MERGE_CANT_MAX ) );
		vector_guardar (indices_vuelta, j, IndiceASacar);
	}
	
	char* nombreIndice = generacion_cadena (vuelta, cadenaVueltaGlobal, nombreRepositorio);
	Indice_t* indice = Merger_realizarMerge (indices_vuelta, nombreIndice);
	if (indice) vector_guardar (indices_mergeados, vuelta, indice);
	
	free (nombreIndice);
	vector_destruir (indices_vuelta);
	return true;
}

vector_dinamico_t* realizar_merge_parcial (vector_dinamico_t* indices, char* nombreRepositorio, int vueltaGlobal) {
	int cantidad = ( (int) vector_cantidad (indices) );
	int nVueltas = cantidad / MERGE_CANT_MAX;
	int resto = ( (vector_cantidad (indices)) % MERGE_CANT_MAX );
	
	int largo = nVueltas;
	if ( resto != 0 ) largo++;
	vector_dinamico_t* indices_mergeados = vector_crear (largo, quemar_indice);
	if (!indices_mergeados) return NULL;
	
	int largoNumero = cantidad_digitos (vueltaGlobal);
	char numeroVueltaGlobal[largoNumero];
	sprintf (numeroVueltaGlobal, "%d", vueltaGlobal);
	char* cadenaVueltaGlobal = concatenacion_cadenas (numeroVueltaGlobal, "_", false);
	for ( int i = 0 ; i < nVueltas ; i++ ) {
		realizar_merge_vuelta (indices, indices_mergeados, MERGE_CANT_MAX, i, cadenaVueltaGlobal, nombreRepositorio);
	}
	
	if ( resto != 0 ) {
		realizar_merge_vuelta (indices, indices_mergeados, resto, nVueltas, cadenaVueltaGlobal, nombreRepositorio);
	}
	
	free (cadenaVueltaGlobal);
	return indices_mergeados;
}

Indice_t* realizar_merge (vector_dinamico_t* indicesRecibidos, char* nombreRepositorio) {
	vector_dinamico_t* indices = indicesRecibidos;
	
	int vueltas = 1;
	bool seguir = true;
	while (seguir) {
		vector_dinamico_t* vTemporal = realizar_merge_parcial (indices, nombreRepositorio, vueltas);
		destruir_archivos_indices (indices); // borro los archivos que ya no sirven
		if (vueltas > 1) vector_destruir (indices); // libero la memoria de las rutas, menos las recibidas por parametro
		indices = vTemporal;
		seguir = ( (vector_cantidad (indices)) > 1 );
		vueltas++;
	}
	Indice_t* indiceMergeado = vector_borrar (indices, 0);
	vector_destruir (indices);
	return indiceMergeado;
}

char* creacionNombreArchivo (char* directorioIndice, char caracter, char* tipo, char* nombreRepositorio) {
	char letra[2];
	letra[0] = caracter;
	letra[1] = '\0';
	char* cadena_parcial_1 = concatenacion_cadenas (directorioIndice, letra, true);
	char* cadena_parcial_2 = concatenacion_cadenas (cadena_parcial_1, tipo, false);
	char* cadena_parcial_3 = concatenacion_cadenas (cadena_parcial_2, nombreRepositorio, false);
	char* cadena_final = concatenacion_cadenas (cadena_parcial_3, EXTENSION, false);
	free (cadena_parcial_1); free (cadena_parcial_2); free (cadena_parcial_3);
	
	return cadena_final;
}

int escribir_numero_final (FILE* fdLectura, ManejadorBits_t* manEscritura,
							vector_binario_t* (*codigo) (long), int* acumulado ) {
	int num = -1;
	fread (&num, sizeof(int), 1, fdLectura);
	if ( num == -1 ) return -1;
	
	if (num == 0) num = 1;
	vector_binario_t* numero = codigo (num);
	if (acumulado) *acumulado += v_binario_largo (numero);
	ManejadorBits_escribir_numero (manEscritura, numero);
	v_binario_destruir (numero);
	
	return num;
}

int escribir_cadena_final (FILE* fdLexLectura, ManejadorBits_t* manLexEscritura, int largo) {
	char* cadena = malloc ( sizeof(char) * (largo+1) );
	if (!cadena) return -1;
	
	int status = fread (cadena, sizeof(char), largo, fdLexLectura);
	if (status < 0) {
		free (cadena);
		return -1;
	}
	cadena[largo] = '\0';
	ManejadorBits_escribir_cadena (manLexEscritura, cadena);
	
	free (cadena);
	return 0;
}

bool compresion_termino (FILE* fdLexLectura, ManejadorBits_t* manLexEscritura, long offset) {
	// repetidos
	int status = escribir_numero_final (fdLexLectura, manLexEscritura, cod_gamma_nuevo, NULL);
	if ( status < 0 ) return false;
	
	// distintos
	int distintos = escribir_numero_final (fdLexLectura, manLexEscritura, cod_gamma_nuevo, NULL);
	if ( distintos < 0 ) return false;
	
	// cadena
	status = escribir_cadena_final (fdLexLectura, manLexEscritura, distintos);
	if ( status < 0 ) return false;
	
	// offset
	int num = 0;
	fread (&num, sizeof(int), 1, fdLexLectura);
	if (offset == 0) offset = 1;
	vector_binario_t* numero = cod_delta_nuevo (offset);
	ManejadorBits_escribir_numero (manLexEscritura, numero);
	v_binario_destruir (numero);
	
	return true;
}

long compresion_bloquePequenioDatos (FILE* fdDatLectura, ManejadorBits_t* manDatEscritura, int* acumulado) {
	int frecuencia = escribir_numero_final (fdDatLectura, manDatEscritura, cod_delta_nuevo, acumulado);
	if (frecuencia < 0) return -1;
	
	for ( int i = 0 ; i < frecuencia ; i++ ) {
		escribir_numero_final (fdDatLectura, manDatEscritura, cod_delta_nuevo, acumulado);
	}
	
	return frecuencia;
}

long compresion_bloqueDatos (FILE* fdDatLectura, ManejadorBits_t* manDatEscritura) {
	int tamanio = 0;
	int frecuencia = compresion_bloquePequenioDatos (fdDatLectura, manDatEscritura, &tamanio);
	if (frecuencia < 0) return -1;
	
	for ( int i = 0 ; i < frecuencia ; i++ ) {
		compresion_bloquePequenioDatos (fdDatLectura, manDatEscritura, &tamanio);
	}
	
	return tamanio;
}

int comprimir_indice (char* directorioIndice, char* nombreRepositorio, char caracter, char* rutaLexico, char* rutaDatos) {
	char* lexicoFinal = creacionNombreArchivo (directorioIndice, caracter, "_lexico_", nombreRepositorio);
	char* datosFinal = creacionNombreArchivo (directorioIndice, caracter, "_datos_", nombreRepositorio);
	if ( (!lexicoFinal) || (!datosFinal) ) return -1;
	
	// manejadores de escritura
	ManejadorBits_t* manLexEscritura = ManejadorBits_crear (lexicoFinal, true);
	ManejadorBits_t* manDatEscritura  = ManejadorBits_crear (datosFinal, true);
	if ( (!manLexEscritura) || (!manDatEscritura) ) return -1;
	
	// manejadores de lectura
	FILE* fdLexLectura = fopen (rutaLexico, "rb");
	FILE* fdDatLectura = fopen (rutaDatos, "rb");
	if ( (!fdLexLectura) || (!fdDatLectura) ) return -1;
	
	bool leerLexico = true;
	long offsetDatos = 0;
	while (leerLexico) {
		leerLexico = compresion_termino (fdLexLectura, manLexEscritura, offsetDatos);
		offsetDatos += compresion_bloqueDatos (fdDatLectura, manDatEscritura);
	}
	
	ManejadorBits_destruir (manLexEscritura);
	ManejadorBits_destruir (manDatEscritura);
	fclose (fdLexLectura);
	fclose (fdDatLectura);
	free (lexicoFinal);
	free (datosFinal);
	return 0;
}

int compresion_final (Indice_t* indiceMergeado, char* directorioIndice, char* nombreRepositorio) {
	if ( (!indiceMergeado) || (!directorioIndice) ) return -1;
	
	char alphanumerico[CANT_ARCH] = NOMBRES_ARCH;
	
	vector_dinamico_t* lexicos = Indice_verRutasLexico (indiceMergeado);
	vector_dinamico_t* datos = Indice_verRutasDatos (indiceMergeado);
	if ( (!lexicos) || (!datos) ) return -1;
	
	for ( int i = 0 ; i < CANT_ARCH ; i++ ) {
		char* rutaLexico = vector_obtener (lexicos, i);
		char* rutaDatos = vector_obtener (datos, i);
		if ( (!rutaLexico) || (!rutaDatos) ) return -1;
		
		int resultado = comprimir_indice (directorioIndice, nombreRepositorio, alphanumerico[i], rutaLexico, rutaDatos);
		if (resultado != 0) return -1;
	}
	destruir_archivos (indiceMergeado);
	return 0;
}

int construccionIndice (char* direccionRepositorio, char* nombreRepositorio, char* directorioCarpetaIndices) {
	if (!nombreRepositorio) return -1;
	printf ("\nConstruccion del indice del repositorio: %s\n\n", nombreRepositorio);
	
	printf ("Creando el directorio y configurando rutas...\n");
	creacionDirectorio (directorioCarpetaIndices, nombreRepositorio);
	char* directorioIndice = concatenacion_cadenas (directorioCarpetaIndices, nombreRepositorio, true);
	printf ("Directorio creado y rutas configuradas!\n");
	
	ManejadorBits_t* docs = crear_manejadorDocs (directorioIndice);
	if (!docs) {
		free (directorioIndice);
		return -1;
	}
	vector_dinamico_t* indices = vector_crear (TAM_INICIAL, quemar_indice );
	if (!indices) {
		ManejadorBits_destruir (docs);
		free (directorioIndice);
		return -1;
	}
	
	printf ("Procesando el directorio...\n");
	int* offset_doc = malloc ( sizeof(int) );
	*offset_doc = 0;
	DIR* repositorio = opendir (direccionRepositorio);
	struct dirent* archivo = readdir (repositorio);
	while (archivo) {
		
		int longNombre = strlen(archivo->d_name);
		if ( longNombre > 2 ) { // si es un archivo, lo proceso
			if ( !(procesar_archivo (direccionRepositorio, docs, indices, archivo->d_name,  offset_doc ) ) ) {
				printf ("El archivo %s no ha podido ser procesado correctamente.\n", archivo->d_name);
			}
		}
		archivo = readdir (repositorio);
	}
	closedir (repositorio);
	printf ("Directorio procesado!\n");
	
	printf ("Realizando merge...\n");
	Indice_t* indiceMergeado = realizar_merge (indices, nombreRepositorio);
	printf ("Merge terminado!\n");
	
	printf ("Realizando compresion final...\n");
	compresion_final (indiceMergeado, directorioIndice, nombreRepositorio);
	printf ("Compresion final terminada!\n");
	
	// cierres y destrucciones
	ManejadorBits_destruir (docs);
	free (directorioIndice);
	free(offset_doc);
	vector_destruir (indices);
	Indice_destruir (indiceMergeado);
	printf ("\nConstruccion del indice finalizada!\n\n");
	return 0;
}

/* *********************************************************************
 *                	 	 RESOLUCION DE CONSULTAS					   *
 * *********************************************************************/

Indice_t* obtener_indice (char* directorioIndice, char* nombreRepositorio) {
	if ( (!nombreRepositorio) || (!directorioIndice) ) return NULL;
	
	vector_dinamico_t* vector_lexico = vector_crear (CANT_ARCH, free);
	vector_dinamico_t* vector_datos = vector_crear (CANT_ARCH, free);
	char alphanum[] = NOMBRES_ARCH;
	
	for (int i = 0 ; i < CANT_ARCH ; i++ ){
		char* direccionLexico = creacionNombreArchivo (directorioIndice, alphanum[i], "_lexico_", nombreRepositorio);
		char* direccionDatos  = creacionNombreArchivo (directorioIndice, alphanum[i], "_datos_", nombreRepositorio);
		if ( (direccionLexico) && (direccionDatos) ){
			vector_guardar (vector_lexico , i , direccionLexico );
			vector_guardar (vector_datos , i , direccionDatos );
		}
	}
	
	return (Indice_crear (vector_lexico , vector_datos));
}

char* obtener_ruta_documentos (char* directorioIndice) {
	char* cadena = concatenacion_cadenas (directorioIndice, DOCUMENTOS, true);
	return cadena;
}

vector_dinamico_t* vectorizar_consulta (char* consulta) {
	if (!consulta) return NULL;
	
	vector_dinamico_t* vectorTerminos = vector_crear ( TAM_INICIAL , free);
	if (!vectorTerminos) return NULL;
	
	Parser_t* parser = Parser_crear (consulta,0,0,0);
	if(!parser){
		vector_destruir(vectorTerminos);
		return NULL;
	}
	
	int pos = 0;
	while ( !(Parser_TerminoDeParsear(parser)) ) {
		char* palabra = Parser_parsear(parser);
		
		if(strlen(palabra) == 0) {      /* Esto esta por las moscas */
			free(palabra);
			continue;
		}
		vector_guardar(vectorTerminos, pos , palabra);
		if ( vector_cantidad(vectorTerminos) == vector_tamanio(vectorTerminos) ) 
			vector_redimensionar (vectorTerminos ,vector_cantidad (vectorTerminos) * 2);
		pos++;
	}
	Parser_destruir (parser);
	return vectorTerminos;
}

char* pedir_consulta () {
	printf ("Ingrese su siguiente consulta (presione enter para salir): ");
	char c;	size_t tam = 10; int i;  // tam = 10 es completamente arbitrario
	
	char* buffer = malloc(sizeof(char)*(tam));
	if (!buffer) return NULL;
	memset (buffer, 0, tam);
	
	for (i = 0 ; (c = getchar()) != '\n' ; i++) { // leo hasta el enter
		
		if (i + 1 == tam) { // si llego al tamanio de mi cadena
			tam += tam;  // la forma de modificar tam es arbitraria
			char* aux = realloc(buffer,tam * sizeof(char));
			if(!aux) break;  // si falla realloc, termino aca
			buffer = aux;
		}
		
		buffer[i] = c;
	}
	
	if (strlen (buffer) == 0) {
		free (buffer);
		return "";
	}
	
	buffer[i]='\0'; // es necesario cerrar el string
	return buffer;
}

static void quemar_hash (void* Hash) {
	hash_destruir ( (hash_t*) Hash );
}

vector_dinamico_t* crear_elementos (Indice_mem_t* indice_mem, vector_dinamico_t* vTerminos) {
	if ( (!indice_mem) || (!vTerminos) ) return NULL;
	
	int cantidad = vector_cantidad (vTerminos);
	vector_dinamico_t* vectorHashDatos = vector_crear (cantidad, quemar_hash);
	if (!vectorHashDatos) return NULL;
	
	for ( int i = 0 ; i < cantidad ; i++ ) {
		char* termino = vector_obtener (vTerminos, i);
		if (!termino) {
			vector_destruir (vectorHashDatos);
			return NULL;
		}
		
		hash_t* Hash = Indice_mem_obtener_datos (indice_mem, termino , i );
		if (!Hash) {
			vector_destruir (vectorHashDatos);
			return NULL;
		}
		vector_guardar (vectorHashDatos, i, Hash);	
	}
	
	return vectorHashDatos;
}

void procesar_hash (hash_t* un_elemento, hash_t* filtro) {
	if ( (!un_elemento) || (!filtro) ) return;
	
	hash_iter_t* iter = hash_iter_crear (un_elemento);
	if (!iter) return;
	
	while ( !(hash_iter_al_final (iter)) ) {
		const char* doc = hash_iter_ver_actual (iter);
		
		int* cantClicks = hash_obtener (filtro, doc);
		if (!cantClicks) {
			cantClicks = malloc (sizeof(int));
			if (!cantClicks) {
				hash_iter_destruir (iter);
				return;
			}
			*cantClicks = 1;
			hash_guardar (filtro, doc, cantClicks);
		} else (*cantClicks)++;
		
		hash_iter_avanzar (iter);
	}
	
	hash_iter_destruir (iter);
}

vector_dinamico_t* filtrar_documentos (hash_t* filtro, int n) {
	if ( (!filtro) || (n < 0) ) return NULL;
	
	int cantDocs = hash_cantidad (filtro);
	vector_dinamico_t* documentosFiltrados = vector_crear (cantDocs, free);
	if (!documentosFiltrados) return NULL;
	
	hash_iter_t* iter = hash_iter_crear (filtro);
	if (!iter) {
		vector_destruir (documentosFiltrados);
		return NULL;
	}
	
	while ( !(hash_iter_al_final (iter)) ) {
		const char* doc = hash_iter_ver_actual (iter);
		
		int* cantClicks = hash_obtener (filtro, doc);
		if ( (*cantClicks) == n ) {
			int pos = vector_cantidad (documentosFiltrados);
			char* cadena = malloc ( (strlen (doc) + 1) * sizeof(char) );
			strcpy (cadena, doc);
			vector_guardar (documentosFiltrados, pos, cadena);
		}
		hash_iter_avanzar (iter);
	}
	hash_iter_destruir (iter);
	return documentosFiltrados;
}

vector_dinamico_t* counting_filters (vector_dinamico_t* elementos) {
	if (!elementos) return NULL;
	
	hash_t* filtro = hash_crear (free); // contiene los clicks
	if (!filtro) return NULL;
	
	int cantElementos = vector_cantidad (elementos);
	for ( int i = 0 ; i < cantElementos ; i++ ) {
		hash_t* un_elemento = vector_obtener (elementos, i);
		procesar_hash (un_elemento, filtro);
	}
	
	vector_dinamico_t* documentosFiltrados = filtrar_documentos (filtro, cantElementos);
	hash_destruir (filtro);
	return documentosFiltrados;
}

void quemar_heap (void* heap) {
	heap_destruir ( (heap_t*) heap, free );
}

int cmp_num (const void *a, const void *b) {
	int* num_a = (int*) a;
	int* num_b = (int*) b;
	return ( (*num_b) - (*num_a) );
}

vector_dinamico_t* creacionHeaps (int cantidad) {
	if (cantidad <= 0) return NULL;
	
	vector_dinamico_t* vHeaps = vector_crear (cantidad, quemar_heap);
	if (!vHeaps) return NULL;
	
	for ( int i = 0 ; i < cantidad ; i++ ) {
		heap_t* un_heap = heap_crear (cmp_num);
		vector_guardar (vHeaps, i, un_heap);
	}
	return vHeaps;
}

void llenar_heaps (vector_dinamico_t* documentosCandidatos, vector_dinamico_t* vHeaps, vector_dinamico_t* elementos) {
	if ( (!documentosCandidatos) || (!vHeaps) || (!elementos) ) return;
	
	int cantCandidatos = vector_cantidad (documentosCandidatos);
	int cantElementos = vector_cantidad (elementos);
	
	for ( int i = 0 ; i < cantCandidatos ; i++ ) {
		char* doc = vector_obtener (documentosCandidatos, i);
		heap_t* un_heap = vector_obtener (vHeaps, i);
		
		for ( int j = 0 ; j < cantElementos ; j++ ) {
			hash_t* un_elemento = vector_obtener (elementos, j);
			vector_dinamico_t* bloquePalabras = hash_obtener (un_elemento, doc);
			
			int cantPalabras = vector_cantidad (bloquePalabras);
			for ( int k = 0 ; k < cantPalabras ; k++ ) {
				int* numPalabra = vector_borrar (bloquePalabras, k);
				heap_encolar (un_heap, numPalabra);
			}
		}
	}
}

bool evaluar_heap (heap_t* heap, int n) {
	if ( (!heap) || (n < 0) ) return false;
	
	int* numPalabraAnterior = (int*) heap_desencolar (heap);
	if (!numPalabraAnterior) return false;
	int cantidad = 1;
	
	while ( !(heap_esta_vacio (heap)) ) {
		int* numPalabraActual = (int*) heap_desencolar (heap);
		
		if ( (*numPalabraAnterior) == (*numPalabraActual) ) {
			cantidad++;
		} else {
			cantidad = 1;
		}
		
		free (numPalabraAnterior);
		numPalabraAnterior = numPalabraActual;
		
		if (cantidad == n) {
			free (numPalabraActual);
			return true;
		}
	}
	free (numPalabraAnterior);
	if (cantidad == n) return true;
	return false;
}

vector_dinamico_t* dame_documentos (hash_t* elemento) {
	if (!elemento) return NULL;
	
	int cantDocs = hash_cantidad (elemento);
	vector_dinamico_t* documentos = vector_crear (cantDocs, free);
	if (!documentos) return NULL;
	
	hash_iter_t* iter = hash_iter_crear (elemento);
	if (!iter) {
		vector_destruir (documentos);
		return NULL;
	}
	
	while ( !(hash_iter_al_final (iter)) ) {
		const char* doc = hash_iter_ver_actual (iter);
		
		int pos = vector_cantidad (documentos);
		char* cadena = malloc ( (strlen (doc) + 1) * sizeof(char) );
		strcpy (cadena, doc);
		vector_guardar (documentos, pos, cadena);
		
		hash_iter_avanzar (iter);
	}
	
	hash_iter_destruir (iter);
	return documentos;
}

vector_dinamico_t* elementos_intersecar (vector_dinamico_t* elementos) {
	if (!elementos) return NULL;
	
	if ( vector_cantidad (elementos) == 1 ) {
		hash_t* elemento = vector_obtener (elementos, 0);
		return ( dame_documentos (elemento) );
	}
	
	vector_dinamico_t* documentosFiltrados = counting_filters (elementos);
	if (!documentosFiltrados) return NULL;
	
	int cantCandidatos = vector_cantidad (documentosFiltrados);
	vector_dinamico_t* vHeaps = creacionHeaps ( cantCandidatos ); // ya esta
	if (!vHeaps) {
		vector_destruir (documentosFiltrados);
		return NULL;
	}
	
	llenar_heaps (documentosFiltrados, vHeaps, elementos);
	
	vector_dinamico_t* documentosFinales = vector_crear (cantCandidatos, free);
	if (!documentosFinales) {
		vector_destruir (documentosFiltrados);
		vector_destruir (vHeaps);
		return NULL;
	}
	
	int cantElementos = vector_cantidad (elementos);
	for ( int i = 0 ; i < cantCandidatos ; i++ ) {
		heap_t* un_heap = vector_obtener (vHeaps, i);
		bool esta_frase = evaluar_heap (un_heap, cantElementos); // ya esta
		if (esta_frase) {
			int* doc = vector_borrar (documentosFiltrados, i);
			int pos = vector_cantidad (documentosFinales);
			vector_guardar (documentosFinales, pos, doc);
		}
	}
	
	vector_destruir (documentosFiltrados);
	vector_destruir (vHeaps);
	return documentosFinales;
}

void imprimir_documentos (Indice_mem_t* indice_mem, vector_dinamico_t* documentos) {
	bool esta = true;
	if (!documentos) esta = false;
	 else if ( vector_cantidad (documentos) == 0 ) esta = false;
	
	if (!esta) {
		printf ("\nLa consulta no se encuentra en la coleccion.\n\n");
		return;
	}
	
	printf ("\nLos documentos en los que se encuentra la consulta son:\n");
	int largo = vector_cantidad (documentos);
	for ( int i = 0 ; i < largo ; i++ ) {
		char* numDoc = vector_obtener (documentos, i);
		int offset = atoi (numDoc);
		char* doc = Indice_mem_obtener_documento (indice_mem, offset);
		if (doc) {
			printf ("%s\n", doc);
			free (doc);
		}
	}
	printf ("\n");
}

void realizar_consultas (Indice_mem_t* indice_mem, char* consult) {
	if (!consult) consult = "";
	if ( (strcmp (consult, "")) != 0 ) printf ("Primer consulta: %s\n", consult);
	 else consult = pedir_consulta ();
	
	while ( (strcmp (consult, "")) != 0 ) {
		vector_dinamico_t* v_consulta = vectorizar_consulta (consult);
		if (!v_consulta) {
			printf ("La consulta no es valida.\n");
			consult = pedir_consulta ();
			continue;
		}
		
		vector_dinamico_t* elementos = crear_elementos (indice_mem, v_consulta);
		vector_dinamico_t* documentos = elementos_intersecar (elementos);
		imprimir_documentos (indice_mem, documentos);
		vector_destruir (v_consulta);
		vector_destruir (elementos);
		vector_destruir (documentos);
		free (consult);
		consult = pedir_consulta();
	}
}

void correr_modo_consulta (char* nombreRepositorio, char* consult, char* directorioCarpetaIndices) {
	if ( (!nombreRepositorio) || (!directorioCarpetaIndices) ) return;
	printf ("\nPrograma en modo consultas sobre el indice: %s\n\n", nombreRepositorio);
	
	printf ("Configurando rutas...\n");
	char* directorioIndice = concatenacion_cadenas (directorioCarpetaIndices, nombreRepositorio, true);
	if (!directorioIndice) return;
	Indice_t* indice = obtener_indice (directorioIndice, nombreRepositorio);
	char* documentos = obtener_ruta_documentos (directorioIndice);
	printf ("Rutas configuradas!\n");
	
	printf ("Abriendo el indice...\n");
	Indice_mem_t* indice_mem = Indice_mem_crear (nombreRepositorio, indice, documentos);
	printf ("Indice cargado!\n\n");
	
	Indice_destruir (indice);
	realizar_consultas (indice_mem, consult);
	
	printf ("\nCerrando el programa...\n\n");
	Indice_mem_destruir (indice_mem);
	
	free(directorioIndice);
	free (documentos);
}

/* *********************************************************************
 *                	 		 FUNCIONES GETOPT						   *
 * *********************************************************************/

void guardar_comando (vector_dinamico_t* parametros, int pos, const char* cadena) {
	if (!cadena) return;
	
	int largo = strlen (cadena);
	char* valor = malloc ( (largo+1) * sizeof(char) );
	if (!valor) return;
	
	strcpy (valor, cadena);
	vector_guardar (parametros, pos, valor);
}

vector_dinamico_t* lectura_comandos (int argc, char** argv) {
	vector_dinamico_t* parametros = vector_crear (CANTIDAD_OPC, free);
	if (!parametros) return NULL;
	
    const char* op_cortas = OPCIONES;
    
    bool hay_comandos = true;
	while (hay_comandos) {
		int siguiente_opc = getopt (argc, argv, op_cortas);
		
		if (siguiente_opc == REPOSITORIO) guardar_comando (parametros, REPOSITORIO_POS, optarg);
		if (siguiente_opc == DIRECTORIO) guardar_comando (parametros, DIRECTORIO_POS, optarg);
		if (siguiente_opc == CONSULTA) guardar_comando (parametros, CONSULTA_POS, optarg);
		
		if (siguiente_opc == -1) hay_comandos = false;
	}
	
	return parametros;
}

/* *********************************************************************
 *                	 		PROGRAMA PRINCIPAL						   *
 * *********************************************************************/

int main (int argc, char** argv) {
	
	/* OPCIONES:
	 * 		MODO CONSTRUCCION: -r nombreRepositorio -d directorioColeccion
	 * 		MODO CONSULTA:	   -r nombreRepositorio -q consulta
	 * */
	
	vector_dinamico_t* parametros = lectura_comandos (argc, argv);
	if (!parametros) return -1;
	
	char* nombreRepositorio		= vector_obtener (parametros, REPOSITORIO_POS);
	char* directorioColeccion	= vector_obtener (parametros, DIRECTORIO_POS);
	char* consulta				= vector_borrar  (parametros, CONSULTA_POS);
	
	if (!nombreRepositorio) {
		vector_destruir (parametros);
		return -1;
	}
	if (!existe_directorio (INDICES)) creacionDirectorio (".",INDICES);
	
	if (directorioColeccion) {
		construccionIndice ( directorioColeccion , nombreRepositorio , INDICES );
	} else {
		correr_modo_consulta ( nombreRepositorio , consulta , INDICES);
	}
	
	vector_destruir (parametros);
	return 0;
}
